package com.m3958.logcollector.tree;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;
import com.m3958.logcollector.repository.TreeBlobRepository;
import com.m3958.logcollector.service.NextSeqNumber;
import com.m3958.logcollector.tree.TreeUpdateAction.TreeUpdateActionResult;
import com.m3958.logcollector.whl.TreeWhlAssistant;

/**
 * tree 和 TreeWhlAssistant必须是一对一的模式，这样基于树的操作才能完全可控的同步。
 * @author jianglibo@gmail.com
 *         2016年1月13日
 *
 */
public class TreeHolder<T> {

    private static final Logger logger = LoggerFactory.getLogger(TreeHolder.class);

    private TreeBlobRepository tbRepo;

    private ObjectMapper objectMapper;
    
    private NextSeqNumber nextSeqNumber;

    private LoadingCache<Long, TreeWhlAssistant<T>> twaCache;
    
    private Path logFolder;
    
    private JavaType treeType;
    private JavaType tuaType;
    
    private long maxTreeWeigher = 20000000;
    
    private Map<Long, ReentrantLock> locks = new HashMap<>();
    
    public TreeHolder(TreeBlobRepository tbRepo, JavaType treeType, JavaType tuaType, ObjectMapper objectMapper, NextSeqNumber nextSeqNumber, Path logFolder, long maxTreeWeigher) throws IOException {
        this.tbRepo = tbRepo;
        this.treeType = treeType;
        this.tuaType = tuaType;
        this.objectMapper = objectMapper;
        this.nextSeqNumber = nextSeqNumber;
        this.logFolder = logFolder;
        this.maxTreeWeigher = maxTreeWeigher;
        initCache();
    }
    
    public Tree<T> getTree(long id) {
        try {
            return twaCache.get(id).getTree();
        } catch (ExecutionException e) {
            logger.error("load tree {} failed, {}", id, e.getMessage());
        }
        return null;
    }
    
    public TreeUpdateAction<T> doUpdate(long id, TreeUpdateAction<T> tua) {
        try {
            return twaCache.get(id).doUpdate(tua);
        } catch (ExecutionException e) {
            tua.setActionResult(TreeUpdateActionResult.FAIL);
            return tua;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void initCache() throws IOException {
//        logFolder = appConfig.getWhlRootPath().resolve("treelog");
        if (!Files.exists(logFolder)) {
            Files.createDirectories(logFolder);
        }
        twaCache = CacheBuilder.newBuilder()//
                .weigher(new Weigher<Long, TreeWhlAssistant<T>>() {
                    @Override
                    public int weigh(Long key, TreeWhlAssistant<T> value) {
                        return value.getTree().getJsonLength();
                    }
                })
                .maximumWeight(maxTreeWeigher)//
                .removalListener(new RemovalListener<Long, TreeWhlAssistant<T>>() {
                    @Override
                    public void onRemoval(RemovalNotification<Long, TreeWhlAssistant<T>> notification) {
                        // we must sync memtree and database tree. at same time, remove all whl.
                    }
                })
                .build(new CacheLoader<Long, TreeWhlAssistant<T>>() {
                    public TreeWhlAssistant<T> load(Long key) throws JsonParseException, JsonMappingException, IOException {
                        return loadTreeFromDb(key);
                    }
                    // 这里是唯一生产TreeWhlAssistant的地方。 
                    private TreeWhlAssistant<T> loadTreeFromDb(Long id) throws JsonParseException, JsonMappingException, IOException {
                        ReentrantLock lock = locks.get(id);
                        if (lock == null) {
                            lock = new ReentrantLock();
                            locks.put(id, lock);
                        }
                        TreeWhlAssistant<T> twa = new TreeWhlAssistant<T>(treeType, tuaType, lock,nextSeqNumber , tbRepo,objectMapper, logFolder.resolve(String.valueOf(id)), id);
                        twa.loadTree();
                        return twa;
                    }
                });
    }

}
